@kite-copilot/chat-panel 0.2.52 → 0.2.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auto.cjs CHANGED
@@ -1066,29 +1066,29 @@ function DataRenderer({ type, data }) {
1066
1066
  // src/components/TypingIndicator.tsx
1067
1067
  var import_jsx_runtime9 = require("react/jsx-runtime");
1068
1068
  function TypingIndicator({ className = "" }) {
1069
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `flex items-center gap-1 px-4 py-3 ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-1", children: [
1069
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `flex items-center gap-1.5 ${className}`, children: [
1070
1070
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1071
1071
  "span",
1072
1072
  {
1073
- className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce",
1073
+ className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce",
1074
1074
  style: { animationDelay: "0ms", animationDuration: "600ms" }
1075
1075
  }
1076
1076
  ),
1077
1077
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1078
1078
  "span",
1079
1079
  {
1080
- className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce",
1080
+ className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce",
1081
1081
  style: { animationDelay: "150ms", animationDuration: "600ms" }
1082
1082
  }
1083
1083
  ),
1084
1084
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1085
1085
  "span",
1086
1086
  {
1087
- className: "w-2 h-2 bg-gray-400 rounded-full animate-bounce",
1087
+ className: "w-2 h-2 bg-gray-500 rounded-full animate-bounce",
1088
1088
  style: { animationDelay: "300ms", animationDuration: "600ms" }
1089
1089
  }
1090
1090
  )
1091
- ] }) });
1091
+ ] });
1092
1092
  }
1093
1093
 
1094
1094
  // src/ChatPanel.tsx
@@ -1537,6 +1537,78 @@ function AuthErrorState({
1537
1537
  )
1538
1538
  ] });
1539
1539
  }
1540
+ function ImageLightbox({
1541
+ imageUrl,
1542
+ onClose
1543
+ }) {
1544
+ const handleDownload = async () => {
1545
+ try {
1546
+ const response = await fetch(imageUrl);
1547
+ const blob = await response.blob();
1548
+ const url = window.URL.createObjectURL(blob);
1549
+ const a = document.createElement("a");
1550
+ a.href = url;
1551
+ const urlParts = imageUrl.split("/");
1552
+ const filename = urlParts[urlParts.length - 1] || "image.jpg";
1553
+ a.download = filename;
1554
+ document.body.appendChild(a);
1555
+ a.click();
1556
+ document.body.removeChild(a);
1557
+ window.URL.revokeObjectURL(url);
1558
+ } catch (error) {
1559
+ console.error("Failed to download image:", error);
1560
+ window.open(imageUrl, "_blank");
1561
+ }
1562
+ };
1563
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1564
+ "div",
1565
+ {
1566
+ className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/80",
1567
+ onClick: onClose,
1568
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1569
+ "div",
1570
+ {
1571
+ className: "relative max-w-[90vw] max-h-[90vh] flex flex-col items-center",
1572
+ onClick: (e) => e.stopPropagation(),
1573
+ children: [
1574
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1575
+ "img",
1576
+ {
1577
+ src: imageUrl,
1578
+ alt: "Full size preview",
1579
+ className: "max-w-full max-h-[80vh] object-contain rounded-lg"
1580
+ }
1581
+ ),
1582
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-3 mt-4", children: [
1583
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1584
+ "button",
1585
+ {
1586
+ onClick: handleDownload,
1587
+ className: "flex items-center gap-2 px-4 py-2 bg-white text-gray-800 rounded-lg hover:bg-gray-100 transition-colors text-sm font-medium",
1588
+ children: [
1589
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Download, { className: "h-4 w-4" }),
1590
+ "Download"
1591
+ ]
1592
+ }
1593
+ ),
1594
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1595
+ "button",
1596
+ {
1597
+ onClick: onClose,
1598
+ className: "flex items-center gap-2 px-4 py-2 bg-gray-700 text-white rounded-lg hover:bg-gray-600 transition-colors text-sm font-medium",
1599
+ children: [
1600
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-4 w-4" }),
1601
+ "Close"
1602
+ ]
1603
+ }
1604
+ )
1605
+ ] })
1606
+ ]
1607
+ }
1608
+ )
1609
+ }
1610
+ );
1611
+ }
1540
1612
  function ChatPanel({
1541
1613
  isOpen = true,
1542
1614
  onClose,
@@ -1557,7 +1629,8 @@ function ChatPanel({
1557
1629
  initialCorner = "bottom-left",
1558
1630
  onCornerChange,
1559
1631
  productBackendUrl,
1560
- getAuthHeaders
1632
+ getAuthHeaders,
1633
+ isEval = false
1561
1634
  } = {}) {
1562
1635
  const [messages, setMessages] = React6.useState(initialMessages);
1563
1636
  const [input, setInput] = React6.useState("");
@@ -1667,13 +1740,20 @@ function ChatPanel({
1667
1740
  }, [effectiveSupabaseUrl, effectiveSupabaseAnonKey]);
1668
1741
  React6.useEffect(() => {
1669
1742
  if (!isEscalated || !sessionId || !supabaseRef.current) {
1743
+ console.log("[KiteChat] Typing channel skip - isEscalated:", isEscalated, "sessionId:", sessionId, "supabase:", !!supabaseRef.current);
1670
1744
  return;
1671
1745
  }
1672
1746
  const channelName = `typing:${sessionId}`;
1673
- const channel = supabaseRef.current.channel(channelName);
1747
+ console.log("[KiteChat] Subscribing to typing channel:", channelName, "sessionId:", sessionId);
1748
+ const channel = supabaseRef.current.channel(channelName, {
1749
+ config: { broadcast: { self: true } }
1750
+ });
1674
1751
  channel.on("broadcast", { event: "typing" }, (payload) => {
1752
+ console.log("[KiteChat] Received typing event:", payload);
1675
1753
  const { sender, isTyping } = payload.payload;
1754
+ console.log("[KiteChat] Typing event - sender:", sender, "isTyping:", isTyping);
1676
1755
  if (sender === "agent") {
1756
+ console.log("[KiteChat] Setting agentIsTyping to:", isTyping);
1677
1757
  setAgentIsTyping(isTyping);
1678
1758
  if (isTyping) {
1679
1759
  if (typingTimeoutRef.current) {
@@ -1685,6 +1765,7 @@ function ChatPanel({
1685
1765
  }
1686
1766
  }
1687
1767
  }).subscribe((status) => {
1768
+ console.log("[KiteChat] Typing channel subscription status:", status);
1688
1769
  if (status === "SUBSCRIBED") {
1689
1770
  typingChannelRef.current = channel;
1690
1771
  console.log("[KiteChat] Typing channel subscribed successfully");
@@ -1699,7 +1780,7 @@ function ChatPanel({
1699
1780
  window.clearTimeout(typingTimeoutRef.current);
1700
1781
  }
1701
1782
  };
1702
- }, [isEscalated, sessionId]);
1783
+ }, [isEscalated, sessionId, effectiveSupabaseUrl, effectiveSupabaseAnonKey]);
1703
1784
  React6.useEffect(() => {
1704
1785
  if (!isOpen && isEscalated && supabaseRef.current && sessionId) {
1705
1786
  console.log("[KiteChat] Panel closed during live chat, marking disconnected");
@@ -1910,6 +1991,77 @@ function ChatPanel({
1910
1991
  const [pendingBulkSession, setPendingBulkSession] = React6.useState(null);
1911
1992
  const pendingBulkSessionRef = React6.useRef(null);
1912
1993
  const fileInputRef = React6.useRef(null);
1994
+ const [pendingImages, setPendingImages] = React6.useState([]);
1995
+ const [isUploadingImage, setIsUploadingImage] = React6.useState(false);
1996
+ const [isDragOver, setIsDragOver] = React6.useState(false);
1997
+ const imageInputRef = React6.useRef(null);
1998
+ const [lightboxImageUrl, setLightboxImageUrl] = React6.useState(null);
1999
+ const uploadImageToStorage = React6.useCallback(async (file) => {
2000
+ if (!supabaseRef.current) {
2001
+ console.error("[KiteChat] Supabase client not available for file upload");
2002
+ return null;
2003
+ }
2004
+ const fileExt = file.name.split(".").pop();
2005
+ const fileName = `${sessionId}/${Date.now()}-${Math.random().toString(36).substring(7)}.${fileExt}`;
2006
+ const bucketName = "chat-attachments";
2007
+ try {
2008
+ const { data, error } = await supabaseRef.current.storage.from(bucketName).upload(fileName, file, {
2009
+ cacheControl: "3600",
2010
+ upsert: false
2011
+ });
2012
+ if (error) {
2013
+ console.error("[KiteChat] Upload error:", error);
2014
+ return null;
2015
+ }
2016
+ const { data: urlData } = supabaseRef.current.storage.from(bucketName).getPublicUrl(fileName);
2017
+ return urlData?.publicUrl || null;
2018
+ } catch (err) {
2019
+ console.error("[KiteChat] Upload failed:", err);
2020
+ return null;
2021
+ }
2022
+ }, [sessionId]);
2023
+ const handleImageSelect = React6.useCallback((files) => {
2024
+ if (!files) return;
2025
+ const imageFiles = Array.from(files).filter(
2026
+ (file) => file.type.startsWith("image/") || file.type === "application/pdf"
2027
+ );
2028
+ const newImages = imageFiles.map((file) => ({
2029
+ file,
2030
+ preview: file.type.startsWith("image/") ? URL.createObjectURL(file) : ""
2031
+ }));
2032
+ setPendingImages((prev) => [...prev, ...newImages]);
2033
+ }, []);
2034
+ const handleDragOver = React6.useCallback((e) => {
2035
+ e.preventDefault();
2036
+ e.stopPropagation();
2037
+ setIsDragOver(true);
2038
+ }, []);
2039
+ const handleDragLeave = React6.useCallback((e) => {
2040
+ e.preventDefault();
2041
+ e.stopPropagation();
2042
+ setIsDragOver(false);
2043
+ }, []);
2044
+ const handleDrop = React6.useCallback((e) => {
2045
+ e.preventDefault();
2046
+ e.stopPropagation();
2047
+ setIsDragOver(false);
2048
+ const files = e.dataTransfer.files;
2049
+ if (files.length > 0) {
2050
+ const csvFile = Array.from(files).find((f) => f.name.endsWith(".csv"));
2051
+ if (csvFile && !isEscalated) {
2052
+ setPendingFile(csvFile);
2053
+ } else {
2054
+ handleImageSelect(files);
2055
+ }
2056
+ }
2057
+ }, [isEscalated, handleImageSelect]);
2058
+ React6.useEffect(() => {
2059
+ return () => {
2060
+ pendingImages.forEach((img) => {
2061
+ if (img.preview) URL.revokeObjectURL(img.preview);
2062
+ });
2063
+ };
2064
+ }, [pendingImages]);
1913
2065
  const [searchExpanded, setSearchExpanded] = React6.useState(false);
1914
2066
  const [searchInput, setSearchInput] = React6.useState("");
1915
2067
  const searchInputRef = React6.useRef(null);
@@ -1966,6 +2118,11 @@ function ChatPanel({
1966
2118
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
1967
2119
  }
1968
2120
  }, [messages, phase, activeGuide]);
2121
+ React6.useEffect(() => {
2122
+ if (isEscalated && agentIsTyping && !activeGuide) {
2123
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
2124
+ }
2125
+ }, [isEscalated, agentIsTyping, activeGuide]);
1969
2126
  const latestBulkSummaryNavigation = React6.useMemo(() => {
1970
2127
  for (let i = messages.length - 1; i >= 0; i--) {
1971
2128
  const msg = messages[i];
@@ -2202,7 +2359,7 @@ function ChatPanel({
2202
2359
  setPanelView("landing");
2203
2360
  setCurrentFolderId(void 0);
2204
2361
  }
2205
- function handleSubmit(e) {
2362
+ async function handleSubmit(e) {
2206
2363
  e.preventDefault();
2207
2364
  const trimmed = input.trim();
2208
2365
  if (pendingFile) {
@@ -2222,6 +2379,49 @@ function ChatPanel({
2222
2379
  if (fileInputRef.current) fileInputRef.current.value = "";
2223
2380
  return;
2224
2381
  }
2382
+ if (pendingImages.length > 0) {
2383
+ setIsUploadingImage(true);
2384
+ try {
2385
+ const uploadedUrls = [];
2386
+ for (const img of pendingImages) {
2387
+ const url = await uploadImageToStorage(img.file);
2388
+ if (url) {
2389
+ uploadedUrls.push(url);
2390
+ }
2391
+ if (img.preview) URL.revokeObjectURL(img.preview);
2392
+ }
2393
+ if (uploadedUrls.length > 0) {
2394
+ const imageMarkdown = uploadedUrls.map((url) => `![image](${url})`).join("\n");
2395
+ const messageContent = trimmed ? `${trimmed}
2396
+
2397
+ ${imageMarkdown}` : imageMarkdown;
2398
+ const userMessage = {
2399
+ id: Date.now(),
2400
+ role: "user",
2401
+ content: messageContent,
2402
+ imageUrls: uploadedUrls
2403
+ };
2404
+ setMessages((prev) => [...prev, userMessage]);
2405
+ if (isEscalated) {
2406
+ sendEscalatedMessage(messageContent);
2407
+ sendTypingIndicator(false);
2408
+ if (userTypingTimeoutRef.current) {
2409
+ window.clearTimeout(userTypingTimeoutRef.current);
2410
+ userTypingTimeoutRef.current = null;
2411
+ }
2412
+ } else {
2413
+ startChatFlow(messageContent);
2414
+ }
2415
+ }
2416
+ } catch (err) {
2417
+ console.error("[KiteChat] Failed to upload images:", err);
2418
+ } finally {
2419
+ setIsUploadingImage(false);
2420
+ setPendingImages([]);
2421
+ setInput("");
2422
+ }
2423
+ return;
2424
+ }
2225
2425
  if (!trimmed) return;
2226
2426
  if (isEscalated) {
2227
2427
  const userMessage = {
@@ -2386,7 +2586,8 @@ function ChatPanel({
2386
2586
  user_id: userId,
2387
2587
  org_id: orgId,
2388
2588
  user_name: userName,
2389
- user_email: userEmail
2589
+ user_email: userEmail,
2590
+ is_eval: isEval
2390
2591
  }),
2391
2592
  signal: controller.signal
2392
2593
  });
@@ -3279,19 +3480,23 @@ ${userText}`
3279
3480
  )
3280
3481
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
3281
3482
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3282
- "input",
3483
+ "textarea",
3283
3484
  {
3284
3485
  ref: searchInputRef,
3285
- type: "text",
3286
3486
  value: searchInput,
3287
- onChange: (e) => setSearchInput(e.target.value),
3487
+ onChange: (e) => {
3488
+ setSearchInput(e.target.value);
3489
+ e.target.style.height = "auto";
3490
+ e.target.style.height = Math.min(e.target.scrollHeight, 120) + "px";
3491
+ },
3288
3492
  onKeyDown: (e) => {
3289
- if (e.key === "Enter" && searchInput.trim()) {
3493
+ if (e.key === "Enter" && !e.shiftKey && searchInput.trim()) {
3290
3494
  e.preventDefault();
3291
3495
  onOpen?.();
3292
3496
  startChatFlow(searchInput.trim());
3293
3497
  setSearchInput("");
3294
3498
  setSearchExpanded(false);
3499
+ e.currentTarget.style.height = "auto";
3295
3500
  } else if (e.key === "Escape") {
3296
3501
  setSearchExpanded(false);
3297
3502
  setSearchInput("");
@@ -3303,7 +3508,8 @@ ${userText}`
3303
3508
  }
3304
3509
  },
3305
3510
  placeholder: "Ask a question...",
3306
- className: "flex-1 text-sm text-gray-700 outline-none bg-transparent"
3511
+ rows: 1,
3512
+ className: "flex-1 text-sm text-gray-700 outline-none bg-transparent resize-none min-h-[20px] max-h-[120px]"
3307
3513
  }
3308
3514
  ),
3309
3515
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
@@ -3563,12 +3769,55 @@ ${userText}`
3563
3769
  return null;
3564
3770
  }
3565
3771
  if (isUser) {
3566
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: `flex justify-end ${isRoleChange ? "mt-3" : ""}`, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-[260px] rounded-2xl rounded-br-md bg-gray-900 px-3 py-2 text-sm text-white shadow-sm", children: message.content }) }, message.id);
3772
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-end gap-2 ${isRoleChange ? "mt-3" : ""}`, children: [
3773
+ message.imageUrls && message.imageUrls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap gap-1 justify-end max-w-[260px]", children: message.imageUrls.map((url, imgIndex) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3774
+ "button",
3775
+ {
3776
+ onClick: () => setLightboxImageUrl(url),
3777
+ className: "block cursor-pointer",
3778
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3779
+ "img",
3780
+ {
3781
+ src: url,
3782
+ alt: `Attachment ${imgIndex + 1}`,
3783
+ className: "max-h-32 max-w-[200px] rounded-lg object-cover border border-gray-700 hover:opacity-90 transition-opacity"
3784
+ }
3785
+ )
3786
+ },
3787
+ imgIndex
3788
+ )) }),
3789
+ message.content && !message.content.match(/^!\[image\]\([^)]+\)(\n!\[image\]\([^)]+\))*$/) && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-[260px] rounded-2xl rounded-br-md bg-gray-900 px-3 py-2 text-sm text-white shadow-sm", children: message.content.replace(/!\[image\]\([^)]+\)\n*/g, "").trim() })
3790
+ ] }, message.id);
3567
3791
  }
3568
3792
  if (message.role === "agent") {
3569
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-start ${isRoleChange ? "mt-3" : ""}`, children: [
3793
+ const imageRegex = /!\[image\]\(([^)]+)\)/g;
3794
+ const extractedImageUrls = [];
3795
+ let match;
3796
+ const contentStr = message.content || "";
3797
+ while ((match = imageRegex.exec(contentStr)) !== null) {
3798
+ extractedImageUrls.push(match[1]);
3799
+ }
3800
+ const agentImageUrls = message.imageUrls || extractedImageUrls;
3801
+ const agentTextContent = contentStr.replace(/!\[image\]\([^)]+\)\n*/g, "").trim();
3802
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex flex-col items-start gap-2 ${isRoleChange ? "mt-3" : ""}`, children: [
3570
3803
  isRoleChange && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-gray-500 mb-1 ml-1", children: "Agent" }),
3571
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-[300px] rounded-2xl rounded-bl-md bg-gray-100 px-4 py-3 text-sm text-gray-700", children: message.content })
3804
+ agentImageUrls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap gap-1 justify-start max-w-[300px]", children: agentImageUrls.map((url, imgIndex) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3805
+ "button",
3806
+ {
3807
+ onClick: () => setLightboxImageUrl(url),
3808
+ className: "block cursor-pointer",
3809
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3810
+ "img",
3811
+ {
3812
+ src: url,
3813
+ alt: `Attachment ${imgIndex + 1}`,
3814
+ className: "max-h-32 max-w-[200px] rounded-lg object-cover border border-gray-300 hover:opacity-90 transition-opacity"
3815
+ }
3816
+ )
3817
+ },
3818
+ imgIndex
3819
+ )) }),
3820
+ agentTextContent && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "max-w-[300px] rounded-2xl rounded-bl-md bg-gray-100 px-4 py-3 text-sm text-gray-700", children: agentTextContent })
3572
3821
  ] }, message.id);
3573
3822
  }
3574
3823
  if (message.kind === "searchSummary") {
@@ -4604,131 +4853,183 @@ ${userText}`
4604
4853
  progressSteps
4605
4854
  }
4606
4855
  ) }),
4607
- isEscalated && agentIsTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TypingIndicator, {}) }),
4856
+ isEscalated && agentIsTyping && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex items-start mt-2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "bg-gray-200 rounded-2xl rounded-bl-md px-4 py-3", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(TypingIndicator, {}) }) }),
4608
4857
  !activeGuide && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { ref: messagesEndRef })
4609
4858
  ] }) }) }) })
4610
4859
  }
4611
4860
  ),
4612
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "px-4 py-3 bg-white shrink-0", children: [
4613
- pendingFile && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-2 flex items-center gap-2 rounded-xl bg-blue-50 border border-blue-200 px-3 py-2", children: [
4614
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.FileSpreadsheet, { className: "h-4 w-4 text-blue-600" }),
4615
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xs text-blue-700 flex-1 truncate", children: pendingFile.name }),
4616
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4617
- "button",
4618
- {
4619
- type: "button",
4620
- onClick: () => {
4621
- setPendingFile(null);
4622
- if (fileInputRef.current) fileInputRef.current.value = "";
4623
- },
4624
- className: "text-blue-600 hover:text-blue-800",
4625
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-4 w-4" })
4626
- }
4627
- )
4628
- ] }),
4629
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("form", { onSubmit: handleSubmit, className: "w-full", children: [
4630
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4631
- "input",
4632
- {
4633
- ref: fileInputRef,
4634
- type: "file",
4635
- accept: ".csv",
4636
- className: "hidden",
4637
- onChange: (e) => {
4638
- const file = e.target.files?.[0];
4639
- if (file) {
4640
- setPendingFile(file);
4861
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
4862
+ "div",
4863
+ {
4864
+ className: `px-4 py-3 bg-white shrink-0 ${isDragOver ? "ring-2 ring-blue-400 ring-inset bg-blue-50" : ""}`,
4865
+ onDragOver: handleDragOver,
4866
+ onDragLeave: handleDragLeave,
4867
+ onDrop: handleDrop,
4868
+ children: [
4869
+ pendingFile && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-2 flex items-center gap-2 rounded-xl bg-blue-50 border border-blue-200 px-3 py-2", children: [
4870
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.FileSpreadsheet, { className: "h-4 w-4 text-blue-600" }),
4871
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-xs text-blue-700 flex-1 truncate", children: pendingFile.name }),
4872
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4873
+ "button",
4874
+ {
4875
+ type: "button",
4876
+ onClick: () => {
4877
+ setPendingFile(null);
4878
+ if (fileInputRef.current) fileInputRef.current.value = "";
4879
+ },
4880
+ className: "text-blue-600 hover:text-blue-800",
4881
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-4 w-4" })
4641
4882
  }
4642
- }
4643
- }
4644
- ),
4645
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex w-full items-start gap-2 rounded-xl border border-gray-200 bg-white px-3 py-2 shadow-sm", children: [
4646
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4647
- Button,
4648
- {
4649
- type: "button",
4650
- size: "icon",
4651
- variant: "ghost",
4652
- onClick: () => fileInputRef.current?.click(),
4653
- className: "h-5 w-5 rounded-full text-gray-400 hover:text-gray-600 hover:bg-gray-100",
4654
- title: "Upload CSV for bulk operations",
4655
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Paperclip, { className: "h-2.5 w-2.5" })
4656
- }
4657
- ),
4658
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4659
- "textarea",
4660
- {
4661
- placeholder: pendingFile ? "Describe what to do with this CSV..." : "Ask anything...",
4662
- value: input,
4663
- onChange: (e) => {
4664
- setInput(e.target.value);
4665
- if (e.target.value.length > 0) {
4666
- handleTypingStart();
4883
+ )
4884
+ ] }),
4885
+ pendingImages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "mb-2 flex flex-wrap gap-2", children: [
4886
+ pendingImages.map((img, index) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "relative group", children: [
4887
+ img.preview ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4888
+ "img",
4889
+ {
4890
+ src: img.preview,
4891
+ alt: `Preview ${index + 1}`,
4892
+ className: "h-16 w-16 object-cover rounded-lg border border-gray-200"
4667
4893
  }
4668
- },
4669
- rows: 1,
4670
- className: "flex-1 border-0 bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 text-sm placeholder:text-gray-400 resize-none overflow-hidden outline-none",
4671
- style: { minHeight: "20px", maxHeight: "120px" },
4672
- onInput: (e) => {
4673
- const target = e.target;
4674
- target.style.height = "auto";
4675
- target.style.height = Math.min(target.scrollHeight, 120) + "px";
4676
- },
4677
- onKeyDown: (e) => {
4678
- if (e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
4679
- e.preventDefault();
4680
- if (input.trim() || pendingFile) {
4681
- const form = e.currentTarget.closest("form");
4682
- if (form) {
4683
- form.requestSubmit();
4684
- }
4685
- }
4686
- return;
4894
+ ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "h-16 w-16 rounded-lg border border-gray-200 bg-gray-100 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.FileSpreadsheet, { className: "h-6 w-6 text-gray-400" }) }),
4895
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4896
+ "button",
4897
+ {
4898
+ type: "button",
4899
+ onClick: () => {
4900
+ if (img.preview) URL.revokeObjectURL(img.preview);
4901
+ setPendingImages((prev) => prev.filter((_, i) => i !== index));
4902
+ },
4903
+ className: "absolute -top-1 -right-1 h-4 w-4 rounded-full bg-gray-800 text-white flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity",
4904
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.X, { className: "h-2.5 w-2.5" })
4687
4905
  }
4688
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
4689
- const currentBulkSession = pendingBulkSessionRef.current;
4690
- if (currentBulkSession) {
4691
- e.preventDefault();
4692
- e.stopPropagation();
4693
- confirmBulkOperation(currentBulkSession);
4694
- return;
4695
- }
4696
- if (pendingAction) {
4697
- e.preventDefault();
4698
- e.stopPropagation();
4699
- handleActionSubmit();
4700
- return;
4701
- }
4702
- if (pendingNavigation) {
4703
- e.preventDefault();
4704
- e.stopPropagation();
4705
- handleConfirmNavigation(pendingNavigation);
4706
- return;
4707
- }
4708
- const currentGuide = activeGuideRef.current;
4709
- if (currentGuide) {
4710
- e.preventDefault();
4711
- e.stopPropagation();
4712
- advanceGuide();
4713
- return;
4906
+ )
4907
+ ] }, index)),
4908
+ isUploadingImage && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "h-16 w-16 rounded-lg border border-gray-200 bg-gray-50 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Loader2, { className: "h-5 w-5 animate-spin text-gray-400" }) })
4909
+ ] }),
4910
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("form", { onSubmit: handleSubmit, className: "w-full", children: [
4911
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4912
+ "input",
4913
+ {
4914
+ ref: fileInputRef,
4915
+ type: "file",
4916
+ accept: ".csv",
4917
+ className: "hidden",
4918
+ onChange: (e) => {
4919
+ const file = e.target.files?.[0];
4920
+ if (file) {
4921
+ setPendingFile(file);
4714
4922
  }
4715
4923
  }
4716
4924
  }
4717
- }
4718
- ),
4719
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4720
- Button,
4721
- {
4722
- type: "submit",
4723
- size: "icon",
4724
- disabled: !input.trim() && !pendingFile || isWaitingForAuth,
4725
- className: "h-6 w-6 rounded-full bg-gray-900 hover:bg-gray-800 disabled:bg-gray-300",
4726
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.ArrowUp, { className: "h-2.5 w-2.5" })
4727
- }
4728
- )
4729
- ] })
4730
- ] })
4731
- ] }),
4925
+ ),
4926
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4927
+ "input",
4928
+ {
4929
+ ref: imageInputRef,
4930
+ type: "file",
4931
+ accept: "image/*,.pdf",
4932
+ multiple: true,
4933
+ className: "hidden",
4934
+ onChange: (e) => handleImageSelect(e.target.files)
4935
+ }
4936
+ ),
4937
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `flex w-full items-start gap-2 rounded-xl border bg-white px-3 py-2 shadow-sm ${isDragOver ? "border-blue-400" : "border-gray-200"}`, children: [
4938
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4939
+ Button,
4940
+ {
4941
+ type: "button",
4942
+ size: "icon",
4943
+ variant: "ghost",
4944
+ onClick: () => {
4945
+ if (isEscalated) {
4946
+ imageInputRef.current?.click();
4947
+ } else {
4948
+ fileInputRef.current?.click();
4949
+ }
4950
+ },
4951
+ className: "h-5 w-5 rounded-full text-gray-400 hover:text-gray-600 hover:bg-gray-100",
4952
+ title: isEscalated ? "Attach image or file" : "Upload CSV for bulk operations",
4953
+ disabled: isUploadingImage,
4954
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.Paperclip, { className: "h-2.5 w-2.5" })
4955
+ }
4956
+ ),
4957
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4958
+ "textarea",
4959
+ {
4960
+ placeholder: pendingFile ? "Describe what to do with this CSV..." : pendingImages.length > 0 ? "Add a message (optional)..." : isDragOver ? "Drop files here..." : "Ask anything...",
4961
+ value: input,
4962
+ onChange: (e) => {
4963
+ setInput(e.target.value);
4964
+ if (e.target.value.length > 0) {
4965
+ handleTypingStart();
4966
+ }
4967
+ },
4968
+ rows: 1,
4969
+ className: "flex-1 border-0 bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 text-sm placeholder:text-gray-400 resize-none overflow-hidden outline-none",
4970
+ style: { minHeight: "20px", maxHeight: "120px" },
4971
+ onInput: (e) => {
4972
+ const target = e.target;
4973
+ target.style.height = "auto";
4974
+ target.style.height = Math.min(target.scrollHeight, 120) + "px";
4975
+ },
4976
+ onKeyDown: (e) => {
4977
+ if (e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
4978
+ e.preventDefault();
4979
+ if (input.trim() || pendingFile || pendingImages.length > 0) {
4980
+ const form = e.currentTarget.closest("form");
4981
+ if (form) {
4982
+ form.requestSubmit();
4983
+ }
4984
+ }
4985
+ return;
4986
+ }
4987
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
4988
+ const currentBulkSession = pendingBulkSessionRef.current;
4989
+ if (currentBulkSession) {
4990
+ e.preventDefault();
4991
+ e.stopPropagation();
4992
+ confirmBulkOperation(currentBulkSession);
4993
+ return;
4994
+ }
4995
+ if (pendingAction) {
4996
+ e.preventDefault();
4997
+ e.stopPropagation();
4998
+ handleActionSubmit();
4999
+ return;
5000
+ }
5001
+ if (pendingNavigation) {
5002
+ e.preventDefault();
5003
+ e.stopPropagation();
5004
+ handleConfirmNavigation(pendingNavigation);
5005
+ return;
5006
+ }
5007
+ const currentGuide = activeGuideRef.current;
5008
+ if (currentGuide) {
5009
+ e.preventDefault();
5010
+ e.stopPropagation();
5011
+ advanceGuide();
5012
+ return;
5013
+ }
5014
+ }
5015
+ }
5016
+ }
5017
+ ),
5018
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5019
+ Button,
5020
+ {
5021
+ type: "submit",
5022
+ size: "icon",
5023
+ disabled: !input.trim() && !pendingFile || isWaitingForAuth,
5024
+ className: "h-6 w-6 rounded-full bg-gray-700 hover:bg-gray-600 disabled:bg-gray-300",
5025
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react4.ArrowUp, { className: "h-2.5 w-2.5 text-white" })
5026
+ }
5027
+ )
5028
+ ] })
5029
+ ] })
5030
+ ]
5031
+ }
5032
+ ),
4732
5033
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
4733
5034
  GuideCursor,
4734
5035
  {
@@ -4737,6 +5038,13 @@ ${userText}`
4737
5038
  visible: cursorState.visible,
4738
5039
  onClick: cursorState.onClick
4739
5040
  }
5041
+ ),
5042
+ lightboxImageUrl && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
5043
+ ImageLightbox,
5044
+ {
5045
+ imageUrl: lightboxImageUrl,
5046
+ onClose: () => setLightboxImageUrl(null)
5047
+ }
4740
5048
  )
4741
5049
  ]
4742
5050
  }
@@ -4761,7 +5069,8 @@ function ChatPanelWithToggle({
4761
5069
  initialCorner,
4762
5070
  onCornerChange,
4763
5071
  productBackendUrl,
4764
- getAuthHeaders
5072
+ getAuthHeaders,
5073
+ isEval
4765
5074
  }) {
4766
5075
  const [internalIsOpen, setInternalIsOpen] = React6.useState(defaultOpen);
4767
5076
  const isOpen = controlledIsOpen !== void 0 ? controlledIsOpen : internalIsOpen;
@@ -4792,7 +5101,8 @@ function ChatPanelWithToggle({
4792
5101
  initialCorner,
4793
5102
  onCornerChange,
4794
5103
  productBackendUrl,
4795
- getAuthHeaders
5104
+ getAuthHeaders,
5105
+ isEval
4796
5106
  }
4797
5107
  );
4798
5108
  }